import { Assets, Container, } from "pixi.js";


import { PixijsRender } from "../PixijsRender";
import { BaseEntity, EntityFlags } from "./BaseEntity";
import { EntityFactory } from "./Factory";
import _ from 'lodash';
import { Signal } from "../signal/Signal";
import { PrefabLoader } from "../Prefab";


export interface JsonWorld {
    version: string,
    entitys: any[],
    offset?: { x: number, y: number },
    scale?: number,
}

export class PixijsWorld {

    private static EntityID: number = 0;
    private __timeTimer: number = -1;
    private __orderdSelectedEntities: BaseEntity[] = [];
    static GetID() {
        return PixijsWorld.EntityID++;
    }

    private __version: number = 0;
    private id2entity = new Map<number, BaseEntity>()
    private __entities: BaseEntity[] = []
    private __pixiRender: PixijsRender;
    private __lastVersion: number = -1;
    private __root: BaseEntity;

    onEntityAdded: Signal = new Signal();
    onEntityRemoved: Signal = new Signal();
    onLoadSucceed: Signal = new Signal();
    onEntityChanged: Signal = new Signal();

    constructor(pixiRendr: PixijsRender) {
        this.__pixiRender = pixiRendr;
        this.__root = new BaseEntity(pixiRendr);
    }

    set viewRoot(root: Container) {
        this.__root.view = root;
        this.__root.children.forEach(c=>{
            root.addChild(c.view);
        })
    }

    get viewRoot() {
        return this.__root.view;
    }

    get sceneRoot() {
        return this.__root;
    }

    get version() {
        return this.__version
    }

    get orderdSelectedEntities(): BaseEntity[] {
        return this.__orderdSelectedEntities;
    }

    getAllUnlockedEntities(flags: number = EntityFlags.All): BaseEntity[] {
        let entities: BaseEntity[] = [];
        const deepFind = (node: BaseEntity) => {
            if (node.flags & flags && !node.locked) {
                entities.push(node);
                node.children.forEach(n => {
                    deepFind(n);
                })
            }
        }

        this.__root.children.forEach(e => {
            deepFind(e);
        })

        return entities;
    }

    get allEntities(): BaseEntity[] {
        if (this.__lastVersion !== this.__version) {
            this.__lastVersion = this.__version;
            this.__entities = [];
            const deepFind = (node: BaseEntity) => {
                this.__entities.push(node);
                node.children.forEach(n => {
                    deepFind(n);
                })
            }

            this.__root.children.forEach(e => {
                deepFind(e);
            })
        }
        return this.__entities;
    }


    get sceneTree(): BaseEntity[] {
        return this.__root.children;
    }

    has(entity: BaseEntity): boolean {
        return this.id2entity.has(entity.id)
    }


    add(entity: BaseEntity, parent?: BaseEntity, at?: number): BaseEntity {
        if (entity) {
            if (this.has(entity)) entity.removeFromParent();
            this.id2entity.set(entity.id, entity);

            if (parent) parent.addChild(entity, at);
            else this.__root.addChild(entity, at);

            this.__version++
            this.onEntityAdded.dispatch(entity)
        }
        return entity
    }


    private __internalRemove(entity: BaseEntity) {
        this.id2entity.delete(entity.id)
        entity.removeFromParent();
        entity.unloadAssets();
        this.__orderdSelectedEntities = this.__orderdSelectedEntities.filter(e => e.id !== entity.id);
    }

    remove(entity: BaseEntity) {
        if (this.has(entity)) {
            this.__internalRemove(entity);
            this.__version++
            this.onEntityRemoved.dispatch(entity)
        }

        return entity
    }

    private __internalRemoveTree(entity: BaseEntity) {
        entity.children.forEach(e => {
            this.__internalRemoveTree(e);
        })
        this.__internalRemove(entity);
    }

    removeTree(entity: BaseEntity) {
        if (this.has(entity)) {
            this.__internalRemoveTree(entity);
            this.__version++
            this.onEntityRemoved.dispatch(entity)
        }
    }


    update<E extends BaseEntity>(
        entity: E,
        update?: keyof E,
        value?: any
    ) {
        /* Apply the update */

        //console.log(update, entity[update], "=>", value);
        if (_.isEqual(entity[update], value)) return entity;
        if (update === 'selected') {
            if (value) {
                this.__orderdSelectedEntities.push(entity);
            }
            else {
                this.__orderdSelectedEntities = this.__orderdSelectedEntities.filter(e => e.id !== entity.id);
            }
            //console.log(this.__orderdSelectedEntities);
        }
        if (typeof update === "string") {
            entity[update] = value
        }
        let changeData = {
            [update]: value
        }
        entity.applyData(changeData);
        this.save();
        this.onEntityChanged.dispatch(entity, update, value);
        return entity
    }

    clear() {
        this.__orderdSelectedEntities = [];
        this.__entities.forEach(e => {
            if (e.view) e.view.removeFromParent()
            e.unloadAssets();
        })
        this.__entities = [];
        let viewRoot = this.__root.view;
        this.__root = new BaseEntity(this.__pixiRender);
        this.__root.view = viewRoot;
        this.__version++
        Assets.reset();
    }

    private __internalSave() {
        let prefab: JsonWorld = {
            version: "0.0.1",
            entitys: [],
            offset: {
                x: this.viewRoot.position.x,
                y: this.viewRoot.position.y
            },
            scale: this.viewRoot.scale.x,
        }
        const entity = this.allEntities;
        for (let i = 0; i < entity.length; i++) {
            const e = entity[i];
            let check = e;
            let tojson = true;
            while (check.parent) {
                if (check.parent.isPrefab) {
                    tojson = false
                    break;
                };
                check = check.parent
            }
            if (tojson) prefab.entitys.push(e.toJson());
        }


        let data = JSON.stringify(prefab);
        localStorage.setItem("pixiCache", data);
    }

    save() {
        if (this.__timeTimer >= 0) clearTimeout(this.__timeTimer);

        this.__timeTimer = setTimeout(() => {
            this.__internalSave();
        }, 1000) as any;
        //PrefabLoader.saveString(, 'scene.actor');
    }

    load() {
        this.id2entity.clear();
        this.__entities = [];

        let data = localStorage.getItem("pixiCache");
        if (data) {
            const prefab: JsonWorld = JSON.parse(data);
            let id2Entity: { [key: string]: any } = {}

            let maxId = 0;
            prefab.entitys.forEach(e => {
                if(e.id > maxId) maxId = e.id;
            })
            PixijsWorld.EntityID = maxId + 1;

            prefab.entitys.forEach(jsonEntity => {
                const entity = EntityFactory.createEntity(jsonEntity.className, this.__pixiRender) as BaseEntity;
                entity.fromJson(jsonEntity);
                if(!this.id2entity.has(jsonEntity.id)) {
                    entity.id = jsonEntity.id;
                }
                id2Entity[jsonEntity.id] = entity;
                this.__entities.push(entity);
            })

            prefab.entitys.forEach(e => {
                e.children?.forEach((child: string | number) => {
                    id2Entity[e.id].children.push(id2Entity[child])
                    id2Entity[e.id].view.addChild(id2Entity[child].view)
                    id2Entity[child].parent = id2Entity[e.id];
                })
            })

            this.__entities.forEach(e => {
                this.id2entity.set(e.id, e);
                if (!e.parent) {
                    this.__root.addChild(e)
                }
            })

            prefab.entitys.forEach(jsonEntity => {
                id2Entity[jsonEntity.id].applyData();
            })

            if (prefab.offset) {
                this.viewRoot.position.x = prefab.offset.x;
                this.viewRoot.position.y = prefab.offset.y;
            }
            if (prefab.scale)
                this.viewRoot.scale.x = this.viewRoot.scale.y = prefab.scale;

        }
        this.onLoadSucceed.dispatch();


    }

    exportSelected(saveAsString: boolean = true) {
        let prefab: JsonWorld = {
            version: "0.0.1",
            entitys: [],
        }
        const entity = this.orderdSelectedEntities;

        const deepGetJson = (e: BaseEntity) => {
            let json = e.toJson();
            prefab.entitys.push(json);
            if (json.isPrefab) return;
            e.children.forEach(c => {
                deepGetJson(c);
            })
        }
        for (let i = 0; i < entity.length; i++) {
            const e = entity[i];
            if (e.parent !== this.__root) {
                let t = {
                    position: { x: 0, y: 0 },
                    scale: { x: 0, y: 0 },
                    pivot: { x: 0, y: 0 },
                    skew: { x: 0, y: 0 },
                    rotation: 0,
                }

                let parentMat = e.view.worldTransform.clone()
                parentMat.decompose(t);
                let json = e.toJson();
                json.position = t.position;
                json.scale = t.scale;
                json.rotation = t.rotation;
                json.parent = undefined;
                prefab.entitys.push(json);
                if (!e.isPrefab) {
                    e.children.forEach(c => {
                        deepGetJson(c);
                    })
                }
            }
            else {
                prefab.entitys.push(e.toJson());
                if (!e.isPrefab) {
                    e.children.forEach(c => {
                        deepGetJson(c);
                    })
                }
            }
        }

        if (saveAsString) {
            let data = JSON.stringify(prefab);
            PrefabLoader.saveString(data, 'selected.json');
        }
        return prefab;
    }

    cloneEntity(entity: BaseEntity, parent?: BaseEntity) {
        let newEntity = EntityFactory.createEntity<any>(entity.className, this.__pixiRender);
        newEntity.fromJson(entity.toJson());

        this.add(newEntity, parent);
        for (const child of entity.children) {
            this.cloneEntity(child, newEntity);
        }
        this.__version++
        this.onEntityAdded.dispatch(entity)
        return newEntity;
    }

    addBlockFromJson(prefab: JsonWorld, parent?: BaseEntity) {
        let id2Entity: { [key: string]: any } = {};
        let tempAddEntities: BaseEntity[] = []

        let maxId = 0;
        prefab.entitys.forEach(e => {
            if(e.id > maxId) maxId = e.id;
        })
        PixijsWorld.EntityID = maxId + 1;

        prefab.entitys.forEach(c => {
            const entity = EntityFactory.createEntity(c.className, this.__pixiRender) as BaseEntity;
            entity.fromJson(c);
            entity.selected = false;
            if(!this.id2entity.has(c.id)) {
                entity.id = c.id;
            }
            id2Entity[c.id] = entity;
            this.__entities.push(entity);
            tempAddEntities.push(entity);
        });

        prefab.entitys.forEach(e => {
            e.children?.forEach((child: string | number) => {
                id2Entity[e.id].children.push(id2Entity[child]);
                id2Entity[e.id].view.addChild(id2Entity[child].view);
                id2Entity[child].parent = id2Entity[e.id];
            });
        });

        tempAddEntities.forEach(e => {
            this.id2entity.set(e.id, e);
            if (!e.parent) {
                if (!parent) {
                    parent = EntityFactory.createEntity("GroupEntity", this.__pixiRender)
                    this.add(parent);
                    parent.visible = true;
                }
                parent.addChild(e);
            }
        });

        tempAddEntities.forEach(e => {
            e.applyData();
        })
        this.onLoadSucceed.dispatch();
        return tempAddEntities;


    }
}